import java.util.List;
import java.util.ArrayList;
import java.io.*;

/**
 * Notes to the coder:
 * The way this works is that class is initialized with an initial term. After
 * initialization the rep looks like this: terms = (t0) operands = (empty_set)
 * <br><br>
 * After the first defineTermOperandPair() call the rep looks like: terms = (t0,
 * t1); operands = (op0)
 * <br><br>
 * So to give an example, lets say we have an ArithmeticExpression exp whose rep
 * looks like: terms = ((POSITIVE, 1), (NEGATIVE, 2), (POSITIVE, 3), (NEGATIVE,
 * 4)) operands = (ADD, SUBTRACT, MULTIPLY) Means... (POSITIVE, 1) ADD
 * (NEGATIVE, 2) SUBRACT (POSITIVE, 3) MULTIPLY (NEGATIVE, 4) = 11 Means in math
 * terms... (+1) + (-2) - (+3) * (-4) = 1 - 2 - 3 * (-4) = 11 Therefore
 * exp.evaluate() should return 11.<br><br>
 *
 * Change Log:<br>
 * 	-4/14/09 - Same Jones<br>
 * 		-wrote method to simplify arithmetic expression<br><br>
 * 	-4/15/09 - Sam Jones<br>
 * 		-Tested and Fixed Errors, Moved contained enums to default package so they can be
 * 		used in calls to ArithmeticExpression class<br><br>
 * 	-4/21/09 - Tobin Chee<br>
 * 		- ArithmeticExpression can now pair labels with the values of its terms, to allow unknown 
 * 		symbols to be stored in there till their value is determined.<br><br>
 * 	-4/14/09 - Tobin Chee<br>
   		- Added documentation. Manually merged due to conflict.<br>
 * 
 * @author Tobin Chee, Sam Jones
 */
public class ArithmeticExpression {
	class Term {
		int magnitude;
		Sign sign;
	}
	/** ArrayList representation of the label corresponding to each term. 
	 */
	private List<String> labels = new ArrayList<String>();
	/** ArrayList representation of each term corresponding to its label.
	 */
	private List<Term> terms = new ArrayList<Term>();
	/** ArrayList representation of each operand separating each term.
	 * This variable should actually be renamed operators, because it actually represents the mathematical operator.
	 */
	private List<ArithmeticOperator> operands = new ArrayList<ArithmeticOperator>();
	/** Boolean representing if there is a label in the Arithmetic Expression.
	 */
	private Boolean containsLabel;
	/**
	 * A list of characters used to represent the A/R/C flags.
	 * <br>
	 * A - Absolute
	 * R - Relocatable or relative, field treated as an integer
	 * C - Complex or external
	 */
	private List<Character> arcFlag = new ArrayList<Character>();
	
	/** 
	 * Represents the four arithmetic operations allowed to appear in expressions.<br><br>
	 * 
	 * Change Log:<br>
	 * 	- 05/05/09 - Tobin Chee<br>
	 * 		- Changed blue javadoc comments to green block comments for readability.<br><br>
	 * 	- 05/17/09 - Tobin Chee<br>
	 * 		- Changed block comments back to javadoc comments.
	 */
	public enum ArithmeticOperator {
		/** ADD - add arithmetic operation
		 */
		ADD,
		/** DIVIDE - divide arithmetic operation 
		 */
		DIVIDE,
		/** MULTIPLY - multiply arithmetic operation
		 */
		MULTIPLY,
		/** SUBTRACT - subtract arithmetic operation
		 */
		SUBTRACT;
	}

	/** 
	 * Represents the sign of a number, positive or negative.
	 * 
	 * Change Log:<br>
	 * 	- 05/05/09 - Tobin Chee<br>
	 * 		- Changed blue javadoc comments to green block comments for readability.<br><br>
	 * 	- 05/17/09 - Tobin Chee<br>
	 * 		- Changed block comments back to javadoc comments.
	 */
	public enum Sign {
		/** NEGATIVE - number is negative or less than 0 
		 */
		NEGATIVE,
		/** POSITIVE - number is positive or greater than or equal to 0
		 */
		POSITIVE;
	}
	
	/** Construct a an arithmetic expression with an initial value.
	 * 
	 * @param initialMagnitude |initial value|.
	 * @param initialSign Sign of initial value.
	 * 
	 * Change Log:<br>
	 * 	- 05/12/09 - Tobin Chee<br>
	 * 		- Method now adds a value to the arcFlag list.
	 */
	public ArithmeticExpression(int initialMagnitude, Sign initialSign) {
		Term t = new Term();
		t.magnitude = initialMagnitude;
		t.sign = initialSign;
		labels.add("");
		arcFlag.add('A'); //'A' for absolute b/c defining a numeric value means it is absolute
		containsLabel = false;

		terms.add(t);
	}
	
	/** Construct an ArithmeticExpression with a label and a sign of first term.
	 * 
	 * @param label Label of first term.
	 * @param initialSign Sign of first term.
	 * 
	 * Change Log:<br>
	 * 	- 05/12/09 - Tobin Chee<br>
	 * 		- Method now adds a value to the arcFlag list.
	 */
	public ArithmeticExpression(String label, Sign initialSign){
		Term t = new Term();
		t.magnitude = -1;
		t.sign = initialSign;
		labels.add(label);
		arcFlag.add('Z'); //TODO abritrary default value
		containsLabel = true;
		
		terms.add(t);
	}

	/**
	 * Converts a number sign to its corresponding Sign enum value.
	 * 
	 * @param sign sign symbol.
	 * @return sign Sign enum value corresponsing to sign.
	 * @throws Exception if sign is not "+" or "-".
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public static Sign stringToSign(String sign) throws Exception{
		
		if(sign.compareTo("+") == 0){
			return Sign.POSITIVE;
		}
		else if(sign.compareTo("-") == 0){
			return Sign.NEGATIVE;
		}
		else{
			throw new Exception();
		}
	}
	
	/**
	 * Flips the sign of the term at the designated index.
	 * 
	 * @param termIndex index of term whose sign will be flipped.
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public void invertSign(int termIndex){
		Term temp = this.terms.get(termIndex);
		
		if(temp.sign == Sign.POSITIVE){
			temp.sign = Sign.NEGATIVE;
		}
		else{ //if sign = NEGATIVE
			temp.sign = Sign.POSITIVE;
		}
	}
	
	/**
	 * Converts an math operator symbol to its corresponding ArithmeticOperator enum value.
	 * 
	 * @param operator math operator symbol.
	 * @return ArithmeticOperator enum value corresponding to operator.
	 * @throws Exception if operator is not "+", "-", "*" or "/".
	 */
	public static ArithmeticOperator stringToArithmeticOperator(String operator) throws Exception{
		
		if(operator.compareTo("+") == 0){
			return ArithmeticOperator.ADD;
		}
		else if(operator.compareTo("-") == 0){
			return ArithmeticOperator.SUBTRACT;
		}
		else if(operator.compareTo("*") == 0){
			return ArithmeticOperator.MULTIPLY;
		}
		else if(operator.compareTo("/") == 0){
			return ArithmeticOperator.DIVIDE;
		}
		else{
			throw new Exception();
		}
	}
	
	/**
	 * Returns the unsigned magnitude of a term in the expression.
	 * 
	 * @param termIndex index of term whose magnitude is returned
	 * @return integer magnitude of term in expression
	 */
	public int getTermMagnitude(int termIndex){
		return terms.get(termIndex).magnitude;
	}
	
	/** Returns the number of operands it contains.
	 * <br>
	 * @return Size of arithmetic expression.
	 * <br>
	 * Change Log:
	 * 	- No changes
	 */
	public int length(){
		return terms.size();
	}
	
	/** Test whether the arithmetic expression contains a label.
	 * <br>
	 * @return True if the arithmetic expression contains a label, otherwise return false.
	 * <br>
	 * Change Log:
	 * 	- No changes
	 */
	public Boolean containsLabels(){
		return containsLabel;
	} 
	
	/** Print the value of an arithmetic expression.
	 * NOTE: Any term_value with a non-empty label will be output as "No_Value" since 
	 * it hasn't been set yet. This means this method's output is only valid for pass1 output.
	 * The function outputs the contents of the ArithmeticExpression stored in this class as a coherent 
	 * mathematical expression of the form:
	 * "Expression: label(term_value) + label(term_value) - etc..."
	 * <br>
	 * @param out Output file to be written to.
	 * <br>
	 * Change Log:
	 * 	- No changes, however method is not longer used
	 */
	public void putToFilePass1(BufferedWriter out){
		
		try{
			out.write("Expression: ");
			
			if(labels.size() > 0){
				int i = 0;
				String signStr;
				
				if(terms.get(i).sign == Sign.POSITIVE){
					signStr = "";
				}
				else{ //if term is NEGATIVE
					signStr = "-";
				}
				
				if(labels.get(i).length() > 0){
					out.write(signStr + labels.get(i) + "(No_Value)");
				}
				else{
					out.write("No_Label(" + signStr + terms.get(i).magnitude + ")");
				}
				
				for(i = 1; i < labels.size(); i++){
					char opChar;
					if(operands.get(i-1) == ArithmeticOperator.ADD){
						opChar = '+';
					}
					else if(operands.get(i-1) == ArithmeticOperator.SUBTRACT){
						opChar = '-';
					}
					else if(operands.get(i-1) == ArithmeticOperator.MULTIPLY){
						opChar = '*';
					}
					else{ // if(operands.get(i-1) == ArithmeticOperator.DIVIDE)
						opChar = '/';
					}
					
					if(labels.get(i).length() > 0){
						out.write(" " + opChar + " " + labels.get(i) + "(No_Value)");
					}
					else{
						out.write(" " + opChar + " " + "No_Label(" + terms.get(i).magnitude + ")");
					}
				}
			}
			else{
				out.write("Empty expression");
			}
			out.write("\n");
		} catch(IOException ioe){
			System.out.println("Assembler Error: ArithmeticExpression.putToFile()");
		}
	}
	
	/** 
	 * Add a term and its separating operator to an ArithmeticExpression.
	 * <br>
	 * @param magnitude |term to be added|.
	 * @param sign Sign of term to be added.
	 * @param operator Operator separating previous term and term to be added.
	 * <br>
	 *  Change Log:
	 * 	- 05/12/09 - Tobin Chee
	 * 		- Method now adds a value to the arcFlag list.
	 */
	public void defineTermOperandPair(int magnitude, Sign sign,
			ArithmeticOperator operator) {
		Term t = new Term();
		t.magnitude = magnitude;
		t.sign = sign;
		terms.add(t);
		operands.add(operator);
		labels.add("");
		arcFlag.add('A'); //TODO Absolute b/c it's a numeric value
		//containsLabel doesn't change
	}
	
	/**
	 * Defines a new term with a magnitude to be determined when the value of it's symbol is defined.
	 * 
	 * @param label: label of the term
	 * @param sign: sign of the term
	 * @param operator: addition/subtraction (+/-)
	 * @param arc: relocation type
	 */
	public void defineLabelOperandARC(String label, Sign sign, ArithmeticOperator operator, char arc){
		Term t = new Term();
		t.magnitude = 0;
		t.sign = sign;
		terms.add(t);
		labels.add(label);
		operands.add(operator);
		arcFlag.add(arc);
	}
	
	/**
	 * Defines a new term with a defined magnitude
	 * 
	 * @param label: label of the term
	 * @param sign: sign of the term
	 * @param operator: addition/subtraction (+/-)
	 * @param arc: relocation type
	 */
	public void defineTermOperandARC(int magnitude, Sign sign, ArithmeticOperator operator, char arc){
		Term t = new Term();
		t.magnitude = magnitude;
		t.sign = sign;
		terms.add(t);
		operands.add(operator);
		labels.add("");
		arcFlag.add(arc);
	}
	
	/** 
	 * Add a label for a term to an ArithmeticExpression.
	 * <br>
	 * @param label Label of term to be added.
	 * @param sign Sign of term to be added.
	 * @param operator Operator separating previous term and term to be added.
	 * <br>
	 * Change Log:
	 * 	- 05/12/09 - Tobin Chee
	 * 		- Method now adds a value to the arcFlag list.
	 */
	public void defineLabelOperandPair(String label, Sign sign, ArithmeticOperator operator){
		Term t = new Term();
		t.magnitude = 0;
		t.sign = sign;
		labels.add(label);
		operands.add(operator);
		terms.add(t);
		arcFlag.add('Z'); //TODO arbitrary default value
		containsLabel = true;
	}
	
	/**
	 * Sets the ARC flag for the term at a given index.
	 * 
	 * @param termIndex index of term whose ARC flag is updated.
	 * @param arc ARC flag value
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public void updateARCFlag(int termIndex, char arc){
		arcFlag.set(termIndex, arc);
	}
	
	/**
	 * Returns the ARC flag for the term at a given index.
	 * 
	 * @param termIndex index of ther term whose ARC flag is returned
	 * @return ARC flag of the term at termIndex
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public char getARCFlag(int termIndex){
		return arcFlag.get(termIndex);
	}
	
	/** 
	 * Updates the magnitude of the term at the designated index.
	 * 
	 * @param termIndex index of term to be updated.
	 * @param value unsigned integer value to which the magnitude of term will be updated.
	 *  
	 *  Change Log:<br>
	 * 	- No changes
	 */
	public void updateTermValue(int termIndex, int value){
		terms.get(termIndex).magnitude = value;
	}
	
	/**
	 * Updates the sign of the term at the designated index.
	 * 
	 * @param termIndex  index of term to be updated.
	 * @param sign Sign value to which term will be updated.
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public void updateSignValue(int termIndex, Sign sign){
		terms.get(termIndex).sign = sign;
	}
	
	/** 
	 * Returns the label corresponding to the term at the given index.
	 * 
	 * @param termIndex Term to look-up.
	 * @return Label corresponding to term.
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public String getSymbolLabel(int termIndex){
		return labels.get(termIndex);
	}
	/**
	 * Returns value of evaluated arithmetic expression held in rep. More
	 * detailed spec at the top.
	 *  
	 * @return The integer value of the evaluated expression.
	 *  
	 * Change log:<br>
	 * 	- 4/15 - Sam Jones<br>
	 * 		- Created and tested.
	 */
	public int evaluate() {
		int count = 0;
		// simply multiply and divides since they're first in
		// order-of-operations
		while (count < this.operands.size()) {
			if (this.operands.get(count) == ArithmeticOperator.MULTIPLY
					|| this.operands.get(count) == ArithmeticOperator.DIVIDE) {
				Term newTerm = simplifyMulDiv(this.terms.get(count), this.terms
						.get(count + 1), this.operands.get(count));
				this.operands.remove(count);
				this.terms.remove(count);
				this.terms.remove(count);
				this.terms.add(count, newTerm);
			} else {
				count++;
			}
		}
		// simplify remaining adds and subtracts
		while (this.operands.size() > 0) {
			Term newTerm = simplifyAddSub(this.terms.get(0), this.terms.get(1),
					this.operands.get(0));
			this.operands.remove(0);
			this.terms.remove(0);
			this.terms.remove(0);
			this.terms.add(0, newTerm);
		}
		// return remaining term
		if (this.terms.get(0).sign == Sign.POSITIVE) {
			return this.terms.get(0).magnitude;
		} else {
			return -1 * this.terms.get(0).magnitude;
		}
	}

	/**
	 * Simplifies a single add or subtract expression. Takes form Term1 op Term2
	 * and returns term produced from operation.
	 *  
	 * @param term1 First term in operation.
	 * @param term2 Second term in operation.
	 * @param op Type of operation.
	 * @return Term produced from the operation.
	 *  
	 * Change Log:<br>
	 * 	- TODO Fill in
	 */
	private Term simplifyAddSub(Term term1, Term term2, ArithmeticOperator op) {
		Term returnTerm = new Term();
		// +(positive) or -(negative) is an add, so perform
		if ((term2.sign == Sign.POSITIVE && op == ArithmeticOperator.ADD)
				|| (term2.sign == Sign.NEGATIVE && op == ArithmeticOperator.SUBTRACT)) {
			if (term1.sign == Sign.POSITIVE) {
				returnTerm.magnitude = term1.magnitude + term2.magnitude;
			} else {
				returnTerm.magnitude = -1 * term1.magnitude + term2.magnitude;
			}
			// +(negative) -(positive) is a subtract, so perform
		} else {
			if (term1.sign == Sign.POSITIVE) {
				returnTerm.magnitude = term1.magnitude - term2.magnitude;
			} else {
				returnTerm.magnitude = -1 * term1.magnitude - term2.magnitude;
			}
		}
		if (returnTerm.magnitude < 0) {
			returnTerm.sign = Sign.NEGATIVE;
			returnTerm.magnitude *= -1;
		} else {
			returnTerm.sign = Sign.POSITIVE;
		}
		return returnTerm;
	}

	/**
	 * Simplifies a single multiply or divide expression. Takes form Term1 op
	 * Term2 and returns term produced from operation.
	 * 
	 * @param term1 First term in operation.
	 * @param term2 Second term in operation.
	 * @param op Type of operation.
	 * @return Term produced from the operation.
	 * 
	 * Change Log:<br>
	 * 	- TODO Fill in
	 */
	private Term simplifyMulDiv(Term term1, Term term2, ArithmeticOperator op) {
		Term returnTerm = new Term();
		// (positive)*/(positive) or (negative)*/(negative) results in a
		// positive so perform
		if ((term1.sign == Sign.POSITIVE && term2.sign == Sign.POSITIVE)
				|| (term1.sign == Sign.NEGATIVE && term2.sign == Sign.NEGATIVE)) {
			if (op == ArithmeticOperator.MULTIPLY) {
				returnTerm.magnitude = term1.magnitude * term2.magnitude;
			} else {
				returnTerm.magnitude = term1.magnitude / term2.magnitude;
			}
			returnTerm.sign = Sign.POSITIVE;
			// (positive)*/(negative) or (negative)*/(positive) results in a
			// negative so perform
		} else {
			if (op == ArithmeticOperator.MULTIPLY) {
				returnTerm.magnitude = term1.magnitude * term2.magnitude;
			} else {
				returnTerm.magnitude = term1.magnitude / term2.magnitude;
			}
			returnTerm.sign = Sign.NEGATIVE;
		}
		return returnTerm;
	}
	
	/**
	 * Returns the specified operator in the Arithmetic Expression
	 *  
	 * @param OperatorIndex the index of the operator to return
	 * @return The operator at the given index
	 *  
	 * Change Log<br>
	 * 	-TODO fill in
	 */
	public ArithmeticOperator getOperator(int operatorIndex){
		return operands.get(operatorIndex);
	}
	
	/**
	 * Returns the sign of the term at the specified index
	 * 
	 * @param termIndex The index of the term for which to return the sign
	 * @return The sign of the term at the specified index
	 * 
	 * Change Log:<br>
	 * 	- No changes
	 */
	public Sign getTermSign(int termIndex){
		return terms.get(termIndex).sign;
	}
}
